#region File Description
//-----------------------------------------------------------------------------
// ModelViewerControl.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Diagnostics;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Stonecode.Solar.ParticleEngine.Emitters;
using Stonecode.Solar.ParticleEngine.Collections;
using Stonecode.Solar.ParticleEngineTest;
using WinFormsGraphicsDevice;
#endregion

namespace Stonecode.Solar.ParticleEffectEditor
{
    /// <summary>
    /// Example control inherits from GraphicsDeviceControl, and displays
    /// a spinning 3D model. The main form class is responsible for loading
    /// the model: this control just displays it.
    /// </summary>
    class ModelViewerControl : GraphicsDeviceControl
    {
        public EmitterCollection EmitterCollection;
        
        public new Color BackColor { get; set; }
        public Camera Camera { get; set; }
        private BasicEffect lineEffect;

        public bool AnimateRotatingGravity { get; set; }

        //private Vector3 _lastGravity = Vector3.UnitY * -30f;
        const float _gravityForce = 30f;
        private float _gravityRotation = 0f;

        Vector3[] OriginalMarkerVerts;
        Vector3[] MarkerVerts;

        static Vector3[] OriginalOffsetVerts = new Vector3[]
        {
            new Vector3(-5,-5, 0),
            new Vector3(5, 5, 0),
            new Vector3(-5, 5, 0),
            new Vector3(5, -5, 0),
        };
        Vector3[] OffsetVerts;

        private VertexPositionColor[] MarkerVPC;

        private VertexPositionColor[] OffsetVPC;

        
        private DynamicVertexBuffer vbuffer;
        private DynamicVertexBuffer offsetBuffer;

        private IndexBuffer ibuffer;
        public bool AutoTrigger
        {
            get;
            set;
        }
        public bool ShowEmitterLocation { get; set; }
        public bool ShowOffsets { get; set; }

        // Cache information about the model size and position.

        // public bool AutoRotate { get; set; }
        // public Vector3 ModelRotation;
        //public bool RotatingModelWithMouse = false;
        //public bool RotatingCameraWithMouse = false;
        //public bool RotatingLookatWithMouse = false;

        public bool leftButtonIsDown = false;

        MouseState _lastMouseState;
        
        


        // Timer controls the rotation speed.
        Stopwatch timer;


        /// <summary>
        /// Initializes the control.
        /// </summary>
        protected override void Initialize()
        {
            
            // Start the animation timer.
            timer = Stopwatch.StartNew();
            
            BackColor = Color.Black;
            //Camera = new Camera();
            this.Camera = new Camera(new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), "Camera");
            Camera.AspectRatio = GraphicsDevice.Viewport.AspectRatio;
            Camera.FieldOfView = 45f;
            Camera.FarPlane = 1000f;
            Camera.NearPlane = 1f;
            Camera.Position = Vector3.UnitZ * -30f;
            Camera.Target = Vector3.Zero;
            Camera.UpdateMatricies();

            _lastMouseState = Mouse.GetState();

            ShowEmitterLocation = true;
            ShowOffsets = true;

            #region Emitter Marker
            OriginalMarkerVerts = new Vector3[]
            {
                new Vector3(-10,0, 0),
                new Vector3(10,0, 0),
                new Vector3(0,10, 0),
                new Vector3(0,-10, 0),

                new Vector3(-1.25f, 1.25f, 0),
                new Vector3(1.25f, 1.25f, 0),

                new Vector3(1.25f, 1.25f, 0),
                new Vector3(1.25f, -1.25f, 0),

                new Vector3(1.25f, -1.25f, 0),
                new Vector3(-1.25f, -1.25f, 0),

                new Vector3(-1.25f, -1.25f, 0),
                new Vector3(-1.25f, 1.25f, 0),
            };

            MarkerVerts = new Vector3[OriginalMarkerVerts.Length];
            MarkerVPC = new VertexPositionColor[MarkerVerts.Length];

            for (int i = 0; i < OriginalMarkerVerts.Length; i++)
            {
                MarkerVerts[i] = OriginalMarkerVerts[i];
                MarkerVPC[i] = new VertexPositionColor(MarkerVerts[i], Color.Lime);
            }

            vbuffer = new DynamicVertexBuffer(GraphicsDevice, typeof(VertexPositionColor), MarkerVerts.Length, BufferUsage.None);
            #endregion

            #region Offset Markers

            OffsetVerts = new Vector3[OriginalOffsetVerts.Length];
            OffsetVPC = new VertexPositionColor[OriginalOffsetVerts.Length];
            offsetBuffer = new DynamicVertexBuffer(GraphicsDevice, typeof(VertexPositionColor), OffsetVerts.Length, BufferUsage.None);
            for (int i = 0; i < OriginalOffsetVerts.Length; i++)
            {
                OffsetVerts[i] = OriginalOffsetVerts[i];
                OffsetVPC[i].Position = OffsetVerts[i];
                OffsetVPC[i].Color = Color.Magenta;
            }

            #endregion

            // Hook the idle event to constantly redraw our animation.
            //Application.Idle += delegate { Invalidate(); };
            Application.Idle += delegate 
            { 
                Invalidate(); 
            };
        }

        public void ResetCamera()
        {
            this.Update();
            this.Camera = new Camera(new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), "Camera");
            Camera.AspectRatio = GraphicsDevice.Viewport.AspectRatio;
            Camera.FieldOfView = 45f;
            Camera.FarPlane = 1000f;
            Camera.NearPlane = 1f;
            Camera.Position = Vector3.UnitZ * -30f;
            Camera.Target = Vector3.Zero;
            Camera.UpdateMatricies();
        }

        public void ResetCamera(Vector3 pos, float near, float far)
        {
            this.Update();
            this.Camera = new Camera(new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), "Camera");
            Camera.AspectRatio = GraphicsDevice.Viewport.AspectRatio;
            Camera.FieldOfView = 45f;
            Camera.FarPlane = far;
            Camera.NearPlane = near;
            Camera.Position = pos; // Vector3.UnitZ * -30f;
            Camera.Target = Vector3.Zero;
            Camera.UpdateMatricies();
        }

        public void ResetCamera(Vector3 pos, float near, float far, float fov)
        {
            this.Update();
            this.Camera = new Camera(new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), "Camera");
            Camera.AspectRatio = GraphicsDevice.Viewport.AspectRatio;
            Camera.FieldOfView = fov;
            Camera.FarPlane = far;
            Camera.NearPlane = near;
            Camera.Position = pos; // Vector3.UnitZ * -30f;
            Camera.Target = Vector3.Zero;
            Camera.UpdateMatricies();
        }

        public void ResetCamera(Vector3 pos, float near, float far, float fov, bool useClientSize)
        {
            this.Update();
            this.Camera = new Camera(new Vector2(this.Width, this.Height), "Camera");
            Camera.AspectRatio = (float)this.Width / (float)this.Height;
            Camera.FieldOfView = fov;
            Camera.FarPlane = far;
            Camera.NearPlane = near;
            Camera.Position = pos; // Vector3.UnitZ * -30f;
            Camera.Target = Vector3.Zero;
            Camera.UpdateMatricies();
        }

        /// <summary>
        /// Update anything that needs updating
        /// </summary>
        /// <param name="dt"></param>
        public void Update(float dt)
        {
            if (leftButtonIsDown)
            {
                MouseState mstate = Mouse.GetState();
                Vector3 pos = GetMousePosition();

                EmitterCollection.Position = pos; // new Vector3(pos.X, pos.Y, EmitterCollection.Position.Z);
            }

            if (AnimateRotatingGravity)
            {
                _gravityRotation += (0.5f * dt);
                float cos = (float)Math.Cos(_gravityRotation);
                float sin = (float)Math.Sin(_gravityRotation);

                Vector3 dir = Vector3.Right;
                dir.X = 1f * cos - 0 * sin;
                dir.Y = 1f * sin + 0 * cos;
                
                EmitterCollection.SetGravity(dir * _gravityForce);
                    
                //Quaternion.CreateFromAxisAngle(Vector3.Backward, _gravityRotation += 0.2f) * Vector3.Right;
            }

            EmitterCollection.Update(dt);
            
            timer.Reset();
            timer.Start();
        }

        private Vector3 GetMousePosition()
        {
            MouseState mstate = Mouse.GetState();
            Vector2 mPos = new Vector2((float)mstate.X, (float)mstate.Y);
            System.Drawing.Point p = this.PointToScreen(System.Drawing.Point.Empty);
            mPos -= new Vector2((float)p.X, (float)p.Y);
            Vector3 nearSource = new Vector3(mPos.X, mPos.Y, Camera.Position.Z);
            Vector3 farSource = new Vector3(mPos.X, mPos.Y, 1f);

            Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearSource, Camera.ProjectionMatrix, Camera.ViewMatrix, Matrix.Identity);
            Vector3 farPoint = GraphicsDevice.Viewport.Unproject(farSource, Camera.ProjectionMatrix, Camera.ViewMatrix, Matrix.Identity);

            Ray ray = new Ray(nearPoint, Vector3.Normalize(farPoint - nearPoint));
            Plane plane = new Plane(Vector3.Forward, 0);

            float? res = ray.Intersects(plane);

            return nearPoint + (ray.Direction * res.Value);
            //float denominator = Vector3.Dot(plane.Normal, ray.Direction);
            //float numerator = Vector3.Dot(plane.Normal, ray.Position) + plane.D;
            //float t = -(numerator / denominator);

            //return nearPoint + ray.Direction * t;
        }

        /// <summary>
        /// Draws the control.
        /// </summary>
        protected override void Draw()
        {
            // Clear to the default control background color.
            float dt = (float)timer.Elapsed.TotalSeconds;
            
            GraphicsDevice.Clear(BackColor);

            if (EmitterCollection != null)
            {
                Update(dt);
                if (EmitterCollection.Count > 0)
                    EmitterCollection.Draw(GraphicsDevice, dt);
            }

            if (lineEffect == null)
            {
                lineEffect = new BasicEffect(GraphicsDevice);
                lineEffect.VertexColorEnabled = true;

            }

            #region ShowEmitterLocation
            if (ShowEmitterLocation)
            {
                SetCrosshairPosition(EmitterCollection.Position);
                
                lineEffect.Projection = Camera.ProjectionMatrix;
                lineEffect.View = Camera.ViewMatrix;
                lineEffect.World = Matrix.CreateWorld(Vector3.Zero, Vector3.Forward, Vector3.Up);

                foreach (EffectTechnique technique in lineEffect.Techniques)
                {
                    foreach (EffectPass pass in technique.Passes)
                    {
                        pass.Apply();

                        //GraphicsDevice.Indices = ibuffer;
                        GraphicsDevice.SetVertexBuffer(vbuffer);
                        GraphicsDevice.BlendState = BlendState.Opaque;

                        //GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.LineStrip, 0, 0, LineVerts.Length, 0, 2);
                        GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, MarkerVPC, 0, MarkerVPC.Length / 2);
                    }
                }
            }

            #endregion

            if (ShowOffsets)
            {
                foreach (EffectTechnique technique in lineEffect.Techniques)
                {
                    foreach (EffectPass pass in technique.Passes)
                    {
                        pass.Apply();
                        
                        foreach (Emitter e in EmitterCollection)
                        {
                            SetOffsetVerts(e.ParticleSystem.Settings.Offset+ EmitterCollection.Position);
                            GraphicsDevice.SetVertexBuffer(vbuffer);
                            GraphicsDevice.BlendState = BlendState.Opaque;
                            GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, OffsetVPC, 0, 2);
                        }
                    }
                }
            }


        }

        private void SetOffsetVerts(Vector3 pos)
        {
            Matrix m = Matrix.CreateTranslation(pos);
            Vector3.Transform(OriginalOffsetVerts, ref m, OffsetVerts);
            for (int i = 0; i < OffsetVerts.Length; i++)
            {
                OffsetVPC[i].Position = OffsetVerts[i];
            }

            if (offsetBuffer.IsContentLost)
                offsetBuffer.SetData(OffsetVPC);
        }

        private void SetCrosshairPosition(Vector3 pos)
        {
            Matrix m = Matrix.CreateTranslation(pos);
            Vector3.Transform(OriginalMarkerVerts, ref m, MarkerVerts);

            for (int i = 0; i < MarkerVerts.Length; i++)
            {
                MarkerVPC[i].Position = MarkerVerts[i];
            }

            //GraphicsDevice.SetVertexBuffer(null);
            if (vbuffer.IsContentLost)
                vbuffer.SetData(MarkerVPC);
        }


    }
}
